﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Windows.Controls.Primitives;
using System.Net;
using System.Xml.Linq;
using System.Configuration;
using eStream.Helpdesk.Notifier.Properties;
using System.Text.RegularExpressions;

namespace eStream.Helpdesk.Notifier
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        DispatcherTimer timer = new DispatcherTimer();
        readonly double NormalTimeInterval = 1.0; //in minutes
        readonly double NoConnectionTimeInterval = 5.0; //in minutes;
        readonly int NotificationPopupAppearanceTime = 8; //in seconds
        readonly string NormalIconSource = "pack://application:,,,/Images/ticket.ico";
        readonly string NoConnectionIconSource = "pack://application:,,,/Images/disticket.ico";
        NotificationPopup popup;
        bool noConnection = false;
        Window settingsWindow = null;
        NotifierSettings settings = new NotifierSettings();

        public MainWindow()
        {
            InitializeComponent();

            this.Loaded += new RoutedEventHandler(MainWindow_Loaded);
        }

        void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            timer.Interval = TimeSpan.FromMinutes(NormalTimeInterval);
            timer.Tick += new EventHandler(timer_Tick);

            bool showSettingsDialog;
            do
            {
                showSettingsDialog = ShowNewTickets();
                if (showSettingsDialog)
                {
                    settingsWindow = new SettingsWindow();

                    if (settingsWindow.ShowDialog() != true)
                    {
                        this.Close();
                        return;
                    }

                    settingsWindow = null;
                }
            } while (showSettingsDialog);

            TraybarIcon.Visibility = System.Windows.Visibility.Visible;
            timer.Start(); 
        }

        /// <summary>
        /// Shows notification popup with the new tickets if any
        /// </summary>
        /// <returns>returns true if connection settings need to be reconfigured, otherwise false</returns>
        private bool ShowNewTickets()
        {
            try
            {
                TryShowNewTickets();
            }
            catch (UriFormatException) { return true; }
            catch (WebException ex)
            {
                //checks for 503 Service Anavailable in order to use retry-after value
                if (ex.Status == WebExceptionStatus.ProtocolError)
                {
                    var response = ex.Response as HttpWebResponse;

                    if (response.StatusCode == HttpStatusCode.ServiceUnavailable)
                    {
                        int retryAfter;
                        var retryAfterString = response.Headers.Get("Retry-After");

                        if (Int32.TryParse(retryAfterString, out retryAfter))
                        {
                            SetNoConnection(TimeSpan.FromSeconds(retryAfter), "Server is busy");
                        }
                        else
                        {
                            SetNoConnection("Server is busy");
                        }

                        return false;
                    }
                }

                //if the webexception is not 503 set no connection with the default retryAfter value
                if (!noConnection)
                {
                    SetNoConnection(ex.Message);
                }

                if (settings.LastAttemptSuccessfull)
                    return false;
                else
                {
                    MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                    return true;
                }

            }
            catch (UnexpectedResponseException)
            {
                if (!noConnection)
                {
                    SetNoConnection("Unexpected response");
                }

                if (settings.LastAttemptSuccessfull)
                    return false;
                else
                    return true;
            }
            catch (ErrorException ex)
            {
                timer.Stop();
                MessageBox.Show(ex.Message, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return true;
            }

            //if successful
            if (noConnection)
            {
                noConnection = false;
                timer.Interval = TimeSpan.FromMinutes(NormalTimeInterval);
                TraybarIcon.IconSource = new BitmapImage(new Uri(NormalIconSource));
                TraybarIcon.ToolTipText = "eStreamDesk Ticket Notifier";
            }

            if (!settings.LastAttemptSuccessfull)
            {
                settings.LastAttemptSuccessfull = true;
                settings.Save();
            }

            return false;
        }

        private void TryShowNewTickets()
        {
            var xmlString = FetchNewTicketsXML();

            XDocument document;

            try
            {
                document = XDocument.Parse(xmlString);
            }
            catch (Exception) { throw new UnexpectedResponseException(); }

            if (IsUnexpectedResponse(document))
            {
                throw new UnexpectedResponseException();
            }

            string error;
            if (IsError(document, out error))
            {
                throw new ErrorException(error);
            }

            var maxCommentID = GetMaxCommentIDFromXMLDocument(document);

            //if not first fetch
            if (settings.LastCommentID.HasValue)
            {
                var tickets = GetTicketsFromXMLDocument(document);

                if (tickets.Length > 0)
                {
                    popup = new NotificationPopup();
                    popup.TicketSource = tickets;
                    TraybarIcon.ShowCustomBalloon(popup, PopupAnimation.Slide, (int)TimeSpan.FromSeconds(NotificationPopupAppearanceTime).TotalMilliseconds);

                    settings.LastCommentID = maxCommentID;
                    settings.Save();
                }
            }
            else
            {
                settings.LastCommentID = maxCommentID ?? 0;
                settings.Save();
            }
        }

        private void SetNoConnection(string errorMessage = null)
        {
            SetNoConnection(TimeSpan.FromMinutes(NoConnectionTimeInterval), errorMessage);
        }

        private void SetNoConnection(TimeSpan retryAfter, string errorMessage = null)
        {
            noConnection = true;
            timer.Interval = retryAfter;

            TraybarIcon.IconSource = new BitmapImage(new Uri(NoConnectionIconSource));
            TraybarIcon.ToolTipText = errorMessage ?? "Unable to connect to service";
        }

        private string FetchNewTicketsXML()
        {
            var webc = new WebClient();
            webc.Headers.Add("email", settings.Email);
            webc.Headers.Add("password", settings.Password);

            return  webc.DownloadString(new Uri(new Uri(settings.HelpdeskAddress), String.Format("/api/tickets?fromCommentId={0}&status=Open", settings.LastCommentID ?? 0)));
        }

        private bool IsUnexpectedResponse(XDocument document)
        {
            return document.Element("Response") == null;
        }

        private bool IsError(XDocument document,  out string error)
        {
            error = String.Empty;

            if (document.Element("Response") == null)
                return false;

            var errorElement = document.Element("Response").Element("Error");

            if (errorElement != null)
            {
                error = (string)errorElement;
                return true;
            }

            return false;
        }

        private long? GetMaxCommentIDFromXMLDocument(XDocument document)
        {
            var ids = (from ts in document.Descendants("Tickets")
                       from t in ts.Descendants("Ticket")
                       from cs in t.Descendants("Comments")
                       from c in cs.Descendants("Comment")
                       select Convert.ToInt64(c.Attribute("Id").Value)).Cast<long>().ToArray();
            if (ids.Length > 0)
                return ids.Max();
            else
                return null;
        }

        private Ticket[] GetTicketsFromXMLDocument(XDocument document)
        {
            return (from ts in document.Descendants("Tickets")
                   from t in ts.Descendants("Ticket")
                   from cs in t.Descendants("Comments")
                   from c in cs.Descendants("Comment")
                   where
                    //Current version of the API on the server side returns all the comments for selected tickets. We need to filter them and select only the new ones.
                    Int64.Parse(c.Attribute("Id").Value) > (settings.LastCommentID ?? 0L) &&
                    //Do not get ticket comments posted by you
                    ((string)c.Element("Email")) != settings.Email 
                   select new Ticket
                   {
                       ID = Int32.Parse(t.Attribute("Id").Value),
                       Subject = (string)t.Element("Subject"),
                       Content = RemoveEmptyLines((string)c.Element("Text"))
                   }).ToArray();
        }

        private string RemoveEmptyLines(string text)
        {
            string pattern = @"(\r?\n\s*){2,}";

            return Regex.Replace(text, pattern, Environment.NewLine);
        }

        protected override void OnClosing(System.ComponentModel.CancelEventArgs e)
        {
            TraybarIcon.Dispose();
            base.OnClosing(e);
        }

        private void timer_Tick(object sender, EventArgs e)
        {
            ShowNewTickets();
        }

        private void MenuItemQuit_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }

        private void MenuSettings_Click(object sender, RoutedEventArgs e)
        {
            if (settingsWindow != null)
            {
                settingsWindow.Focus();
                return;
            }

            bool showSettingsDialog = false;
            bool newSettings;
            do
            {
                settingsWindow = new SettingsWindow();
                newSettings = settingsWindow.ShowDialog() == true;
                settingsWindow = null;

                if (newSettings)
                {
                    showSettingsDialog = ShowNewTickets();
                }

            } while (showSettingsDialog);


            if (newSettings && !timer.IsEnabled)
            {
                timer.Interval = TimeSpan.FromMinutes(NormalTimeInterval);
                timer.Start();
            }
        }
    }
}
